/*
 moxl - Modular XML Library
 Copyright (c) 2007, Malcolm Sparks

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package moxl.dom.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import moxl.dom.MoxlCharacterData;
import moxl.dom.MoxlElement;
import moxl.dom.MoxlNode;

/**
 * Instances of this class simply manage an ordered list of nodes, but the class itself does not implement a java.util.List. This is a useful class that can be
 * used by implementations of {@link MoxlElement} to retain child nodes.
 */
public class MoxlNodeList implements Iterable<MoxlNode>
{
	private List<MoxlNode> nodes;

	private final MoxlElement parent;
	private MoxlNodeListMutatorGuard mutableNodesList;

	public MoxlNodeList(MoxlElement parent)
	{
		if (parent == null)
		{
			throw new IllegalArgumentException("parent cannot be null");
		}
		this.parent = parent;
	}

	private void ensure()
	{
		if (nodes == null)
		{
			nodes = new LinkedList<MoxlNode>();
		}
	}

	private void ensureMutator()
	{
		ensure();
		if (mutableNodesList == null)
		{
			mutableNodesList = new MoxlNodeListMutatorGuard(nodes, parent);
		}
	}

	public void append(MoxlNode node)
	{
		ensure();
		replaceParentOnNode(node, parent);
		nodes.add(node);
	}

	public Iterator<MoxlNode> iterator()
	{
		if (nodes == null)
		{
			List<MoxlNode> emptyList = Collections.emptyList();
			return emptyList.iterator();
		}
		else
		{
			return nodes.iterator();
		}
	}

	public MoxlNode getNodeAtIndex(int index)
	{
		if (nodes == null || index >= nodes.size())
		{
			throw new NoSuchElementException();
		}
		return nodes.get(index);
	}

	public MoxlNode remove(int index)
	{
		if (nodes == null || index >= nodes.size())
		{
			throw new NoSuchElementException();
		}
		MoxlNode result = nodes.remove(index);
		result.setParent(null);
		return result;
	}

	public boolean isEmpty()
	{
		return nodes == null || nodes.isEmpty();
	}

	public List<MoxlNode> getNodes()
	{
		ensureMutator();
		return mutableNodesList;
	}

	public void insert(MoxlNode node)
	{
		ensure();
		node.setParent(parent);
		nodes.add(0, node);
	}

	public boolean containsCharacterData()
	{
		if (nodes == null)
		{
			return false;
		}
		for (MoxlNode node : nodes)
		{
			if (node instanceof MoxlCharacterData)
			{
				return true;
			}
		}
		return false;
	}

	public boolean containsCharacterDataExcludingWhitespace()
	{
		if (nodes == null)
		{
			return false;
		}
		for (MoxlNode node : nodes)
		{
			if (node instanceof MoxlCharacterData)
			{
				MoxlCharacterData cdata = (MoxlCharacterData) node;
				if (!cdata.isWhitespace())
				{
					return true;
				}
			}
		}
		return false;
	}

	public int size()
	{
		if (nodes == null)
		{
			return 0;
		}
		return nodes.size();
	}

	public int countElements()
	{
		if (nodes == null)
		{
			return 0;
		}
		else
		{
			int count = 0;
			for (MoxlNode node : nodes)
			{
				if (node instanceof MoxlElement)
				{
					count++;
				}
			}
			return count;
		}
	}

	public List<MoxlElement> getElements()
	{
		if (nodes == null)
		{
			return Collections.emptyList();
		}
		List<MoxlElement> result = new ArrayList<MoxlElement>();
		for (MoxlNode node : nodes)
		{
			if (node instanceof MoxlElement)
			{
				result.add((MoxlElement) node);
			}
		}
		return result;
	}

	public int countNodes()
	{
		if (nodes == null)
		{
			return 0;
		}
		else
		{
			return nodes.size();
		}
	}

	public void replace(MoxlNode old, MoxlNode replacement)
	{
		if (nodes != null)
		{
			int index = nodes.indexOf(old);
			if (index != -1)
			{
				nodes.remove(index);
				nodes.add(index, replacement);
				return;
			}
		}
		throw new NoSuchElementException("Node not found: " + old);
	}

	public int indexOf(MoxlNode node)
	{
		if (nodes == null)
		{
			return -1;
		}
		return nodes.indexOf(node);
	}

	public void insert(int index, MoxlNode node)
	{
		ensure();
		if (node.getParent() != null)
		{
			throw new IllegalStateException("Node still has a parent element, must be fully detached before attaching to another element");
		}
		node.setParent(parent);
		nodes.add(index, node);
	}

	public List<MoxlNode> detachAll()
	{
		if (nodes == null)
		{
			return Collections.emptyList();
		}
		List<MoxlNode> result = new ArrayList<MoxlNode>(nodes.size());
		for (MoxlNode node : nodes)
		{
			node.setParent(null);
			result.add(node);
		}
		nodes.clear();
		return result;
	}

	public static void replaceParentOnNode(MoxlNode node, MoxlElement newParent)
	{
		if (node.getParent() != null)
		{
			throw new IllegalStateException("Node still has a parent element, must be fully detached before attaching to another element");
		}
		node.setParent(newParent);
	}

	public String getContentAsString()
	{
		StringBuilder builder = new StringBuilder();
		for (MoxlNode node : nodes)
		{
			if (node instanceof MoxlCharacterData)
			{
				builder.append(((MoxlCharacterData) node).getData());
			}
			else
			{
				throw new IllegalStateException("Node " + parent + " contains a child that is not character data");
			}
		}
		return builder.toString();
	}

}
